#include "InterOpReader.h"
#include "InterOpReader.h"
#include "../InterOpUtils.h"

#include "utils/FileUtil.h"
#include "utils/StringUtil.h"
#include "common/logger.h"
#include "SPAIopPolicy.h"
#include "SPAIopInputProductStructure.h"
#include "SPAIopDocument.h"
#include "SPAIopProductStructureImporter.h"
#include "SPAIopWString.h"
#include "../InterOpUtils.h"
#include "SPAIopPSImportResult.h"
#include "SPAIopInputPSInstance.h"
#include "utils/SystemUtils.h"
#include "vector.hxx"
#include "matrix.hxx"
#include "transf.hxx"
#include "SPAIopInputPSReference.h"
#include <cstddef>
#include "topology/TopoCompound.hpp"
#include "topology/TopoBuilder.hpp"
#include "SPAIopPartDocument.h"
#include "SPAIopAcisPartImporter.h"
#include "SPAIopPartImportResult.h"
#include "kernapi.hxx"
#include "body.hxx"
#include "InterOpReaderHelper.h"
#include <SPAIopImportOptionsCatiaV5.h>
#include <sstream>
#include <vector>
#include "SPAIopPMICapture.h"
#include "InterOpPMIHelper.h"
#include "InterOpDocumentInstance.h"
#include "InterOpDocumentReference.h"
#include "InterOpMatrix.h"

AMCAX_EXCHANGE_NAMESPACE_BEGIN

namespace InterOp {

Reader::Reader(std::string file_path)
: filePath_(file_path)
{

}


void Reader::ReadFromFilePath()
{
    
    if (!FileUtil::IsFileExist(filePath_))
    {
        LOG_ERROR("file not exist:{}", filePath_);
        return ;
    }
    referenceEntityCache.clear() ;
    oPMIInfos_.clear() ;

    try
    {
        
        SPAIopPolicy iPolicy;
        SPAIopInputProductStructure inputProductStructure;

        const wchar_t *ipDocType = nullptr;
        SPAIopWString wSourceDocType(ipDocType);
        SPAIopWString wSourceFilePath = InterOp::StringToSPAIopWString(filePath_);
        SPAIopDocument sourceDoc(wSourceFilePath, wSourceDocType);
    
        SPAIopProductStructureImporter importer;
        SPAIopPSImportResult input_result = importer.ImportWithPolicy(iPolicy, sourceDoc, inputProductStructure);
        // LOG_INFO("import done. start convert...");

        SPAIopInputPSMissingFileIter missingFiles = input_result.GetMissingFileIterator();

        while (missingFiles.Next())
        {
            auto missingFilePath = missingFiles.Current();
            LOG_INFO("missing file: {}", SPAIopWStringToString(missingFilePath));
        }


        ConvertInterOpToObject(inputProductStructure, input_result);
    }
    catch (SPAIopAcisError &error)
    {
        InterOp::ShowAcisErrorMsg(error) ;
    }
    catch (...)
    {
        LOG_ERROR("InterOP FAILURE: Unknown exception caught!");
    }
    // LOG_INFO("InterOp read done.");
}

void Reader::ConvertInterOpToObject(const SPAIopInputProductStructure &productStructure, SPAIopPSImportResult& inputPSResult)
{
	SPAIopInputPSMissingFileIter missingFiles = inputPSResult.GetMissingFileIterator();

    int numberRootInstances = productStructure.GetRootInstancesCount();
    LOG_DEBUG("Root Instances Count:{}", numberRootInstances);

    // Add extra parent assembly structure if product structure has multiple root instances.
    if(numberRootInstances > 1)
    {
        rootInterOpDocInstance_ = std::make_shared<InterOpDocumentInstance>();
        auto rooltInterOpDocRef = std::make_shared<InterOpDocumentReference>();
        auto productName = productStructure.GetProductName();
        rooltInterOpDocRef->SetName(SPAIopWStringToString(productName));
        // SPAIopWString productPID;
        // productStructure.GetPID(productPID);
        // auto rootDocRefPID = SPAIopWStringToString(productPID);
        //rooltInterOpDocRef->SetId(rootDocRefPID);
        rootInterOpDocInstance_->SetDocumentReference(rooltInterOpDocRef);
        double 	oAffineTransform[3][3] = { 1, 0, 0,
                                            0, 1, 0,
                                            0, 0, 1 };
        double 	oTranslation[3] = { 0 };
        rootInterOpDocInstance_->SetTransform(InterOpMatrix(oAffineTransform, oTranslation));
        docRefIdMap_.emplace(0, rooltInterOpDocRef);
    }

    for (int i = 0; i < numberRootInstances; i++)
    {
        SPAIopInputPSInstance rootInstance = productStructure.GetRootInstance(i);

        SPAIopInputPSReference inputReference = rootInstance.GetReference();
		SPAIopWString referenceStorageName = inputReference.GetStorageName();
		bool isMissingReference = missingFiles.ContainsPath(referenceStorageName);
		if (false == isMissingReference)
		{
            auto interOpRootInstance = ParseInstanceRecursion(rootInstance, missingFiles);
            if(rootInterOpDocInstance_)
            {
                auto rootDocRef = rootInterOpDocInstance_->GetDocumentReference();
                if(!rootDocRef)
                {
                    continue;
                }
                rootDocRef->AddChildInstance(interOpRootInstance);
            }
            else
            {
                rootInterOpDocInstance_ = interOpRootInstance;
            }

        }
    }
}

std::shared_ptr<InterOpDocumentInstance> Reader::ParseInstanceRecursion(const SPAIopInputPSInstance& iopInputInstance, SPAIopInputPSMissingFileIter& iMissingFiles)
{
    auto interOpDocInstance = std::make_shared<InterOpDocumentInstance>();
    SPAIopWString instancePersistName;
    iopInputInstance.GetPersistentName(instancePersistName);
    interOpDocInstance->SetPersistName(SPAIopWStringToString(instancePersistName));
    interOpDocInstance->SetName(SPAIopWStringToString(iopInputInstance.GetInstanceName()));

    double 	oAffineTransform[3][3] = { 0 };
    double 	oTranslation[3] = { 0 };
    iopInputInstance.GetInstancePositionMatrix(oAffineTransform, oTranslation);
    interOpDocInstance->SetTransform(InterOpMatrix(oAffineTransform, oTranslation));

    auto reference = iopInputInstance.GetReference();
    unsigned int referenceUid = reference.GetUniqueID();
    std::shared_ptr<InterOpDocumentReference> interOpDocRef = nullptr;
    if(docRefIdMap_.find(referenceUid) != docRefIdMap_.end())
    {
        interOpDocRef = docRefIdMap_.at(referenceUid);
    }
    else
    {
        interOpDocRef = std::make_shared<InterOpDocumentReference>();
        interOpDocRef->SetName(SPAIopWStringToString(reference.GetReferenceName()));
        interOpDocRef->SetId(referenceUid);
        if (reference.IsPart())
        {
            // Get TopShape from SPAIopInputPSReference
            AMCAX::TopoCompound partShape;
            AMCAX::TopoBuilder TB;
            TB.MakeCompound(partShape);
            std::unordered_map<AMCAX::TopoShape, InterOpMaterial> materials;
            ImportPartBrep(reference, partShape, materials);
            // Set TopShape for interOpDocRef.
            interOpDocRef->SetPartShape(partShape);
        }

        // TODO: set PMI data? or set PMI data to instance?
        
        docRefIdMap_.emplace(referenceUid, interOpDocRef);
    }
    interOpDocInstance->SetDocumentReference(interOpDocRef);

    // Loop for all child instances from the reference.
    int instanceCount = reference.GetInstancesCount();
    for (int i = 0; i < instanceCount; i++)
    {
        auto childInstance = reference.GetInstance(i);

        SPAIopInputPSReference iNodeReference = childInstance.GetReference();
        SPAIopWString referenceStorageName = iNodeReference.GetStorageName();
        bool isMissingReference = iMissingFiles.ContainsPath(referenceStorageName);
        if (false == isMissingReference)
        {
            auto childInterOpInstance = ParseInstanceRecursion(childInstance, iMissingFiles);
            interOpDocRef->AddChildInstance(childInterOpInstance);
        }
    }

    return interOpDocInstance;
}

void Reader::ImportPartBrep(const SPAIopInputPSReference& reference, AMCAX::TopoShape& parentShape, std::unordered_map<AMCAX::TopoShape, InterOpMaterial>& materials)
{
    ENTITY_LIST entityList;
    LOG_DEBUG("ready to import file: {}", SPAIopWStringToString(reference.GetStorageName()));
    if (!reference.IsPart())
    {
        return;
    }

    SPAIopPartDocument acisPartDocument = reference.GetDocument();
    SPAIopAcisPartImporter partImporter;
    SPAIopPolicy policy;
    SPAIopPartImportResult importResult = partImporter.ImportWithPolicy(policy, acisPartDocument, entityList);

    std::string pmiReadOn = SystemUtil::GetEnv("AMCAX_PMI_READER_ON") ;
    if(StringUtil::IsEqualIgnoreCaseAfterTrimStr(pmiReadOn,"on"))
    {
        SPAIopPMIData pmiData = importResult.GetPMIData();
        SPAIopMappingData mappingData = importResult.GetMappingData();
        // std::unordered_map<std::string, InterOpShapePMIInfos> oPMIInfos;
        TraverserPMIData(oPMIInfos_, pmiData, mappingData);
    }
    
    for (auto entity : entityList) 
    {
        if (!is_BODY(entity))
        continue;
        InterOp::ReaderConvert::ConvertBody(entity, parentShape, materials);
    }
    
}

}
AMCAX_EXCHANGE_NAMESPACE_END